﻿using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Input;
using AMEE.BeinControl;
using AMEE.MVVMBase;
using LogViewer = AMEE.LogViewer;
using MySets = AMEE.Demo1WPF.Properties.Settings;

namespace AMEE.Demo1WPF.ViewModel
{
    /// <summary>
    /// 
    /// </summary>
    class MainWindowViewModel : ViewModelBase
    {
        #region Constants

        // Interne Reihenfolge der Beine
        static readonly int VL = 0;
        static readonly int VR = 1;
        static readonly int HL = 2;
        static readonly int HR = 3;

        /// <summary>Parameter for RunCommand to perform one step cyclus.</summary>
        public const String ONE = "One";
        /// <summary>Parameter for RunCommand to perform multiple step cyclus (See Config file)</summary>
        public const String MULTI = "Multiple";

        #endregion

        #region Fields

        /// <summary>Coordinator instance, that performs 'walking'.</summary>
        Stepper _walker;
        /// <summary>Beine vl,vr,hl,hr</summary>
        Bein[] _legs;
        /// <summary>Setting for how many times to do Stepcyclus <c>multiple</c> stands for.</summary>
        int _multiple = Properties.Settings.Default.Multiple;

        #endregion


        #region Commands and Properties

        RelayCommand _startPoseCmd;
        /// <summary>
        /// Gets the (go to) start pose command.
        /// </summary>
        /// <value>
        /// The start pose command.
        /// </value>
        public ICommand StartPoseCommand {
            get {
                if (_startPoseCmd == null)
                    _startPoseCmd = new RelayCommand(o => startPoseExecute(), o => startPoseCanExecute());
                return _startPoseCmd;
            }
        }

        RelayCommand<String> _stompCmd;
        /// <summary>
        /// Gets the stomp command.
        /// </summary>
        /// <value>
        /// The stomp command.
        /// </value>
        public ICommand StompCommand {
            get {
                if (_stompCmd == null)
                    _stompCmd = new RelayCommand<string>(cnt => stompExecute(cnt), p => IsConnected && IsReadyToWalk);
                return _stompCmd;
            }
        }

        RelayCommand _openLogCmd;
        bool _isLoggerShown;
        /// <summary>
        /// Gets the open log command.
        /// </summary>
        /// <value>
        /// The open log command.
        /// </value>
        public ICommand OpenLogCommand {
            get {
                if (_openLogCmd == null) {
                    _openLogCmd = new RelayCommand(o => {
                        //if (_isLoggerShown)
                        //    Logger.Hide();
                        //else
                        //    Logger.Show();
                        //_isLoggerShown = !_isLoggerShown;
                    });
                }
                return _openLogCmd;
            }
        }

        RelayCommand _parkCmd;
        public ICommand ParkCommand {
            get {
                if (_parkCmd == null)
                    _parkCmd = new RelayCommand(o => parkExecute(), o => parkCanExecute());
                return _parkCmd;
            }
        }

        RelayCommand<string> _echoCmd;
        public ICommand EchoCommand {
            get {
                if (_echoCmd == null)
                    _echoCmd = new RelayCommand<string>(param => echoExecute(param), param => echoCanExecute(param));
                return _echoCmd;
            }
        }

        RelayCommand _applySettings;
        /// <summary>
        /// WPF Command to apply user defined settings.
        /// </summary>
        public ICommand ApplySettingsCommand {
            get {
                if (_applySettings == null)
                    _applySettings = new RelayCommand(o => applySettingsExecute());
                return _applySettings;
            }
        }


        RelayCommand _shutDownCmd;
        /// <summary>
        /// WPF Command to disconnect from legs.
        /// </summary>
        public ICommand ShutDownCommand {
            get {
                if (_shutDownCmd == null)
                    _shutDownCmd = new RelayCommand(param => shutDownExecute(), pred => shutDownCanExecute());
                return _shutDownCmd;
            }
        }

        // TODO: ReConnect Befehl/Option (siehe reConnectExecute())

        RelayCommand<string> _runCmd;
        /// <summary>
        /// WPF Command to execute One or Multiple step cyclus.<para>
        /// Pass CommandParameter "One" or "Multiple".</para>
        /// </summary>
        public ICommand RunCommand {
            get {
                if (_runCmd == null)
                    _runCmd = new RelayCommand<string>(cnt => runExecute(cnt), p => IsConnected && IsReadyToWalk);
                return _runCmd;
            }
        }

        bool _isConnected;
        /// <summary>
        /// Indicates, if TCPConnection to legs are established.
        /// </summary>
        /// <value>
        ///   <c>true</c> if [is connected]; otherwise, <c>false</c>.
        /// </value>
        public bool IsConnected {
            get { return _isConnected; }
            set {
                _isConnected = value;
                base.OnPropertyChanged("IsConnected");
            }
        }

        bool _isReady;
        /// <summary>
        /// Gets or sets a value indicating whether AMEE is ready to walk.
        /// </summary>
        /// <value>
        /// <c>true</c> if [is ready to walk]; otherwise, <c>false</c>.
        /// </value>
        public bool IsReadyToWalk {
            get { return _isReady; }
            set {
                _isReady = value;
                OnPropertyChanged("IsReadyToWalk");
            }
        }

        /// <summary>
        /// Gets or bein view models with State and ID data.
        /// </summary>
        /// <value>
        /// The bein viewModels.
        /// </value>
        public ObservableCollection<BeinViewModel> BeinViewModels { get; private set; }

        #endregion

        public MainWindowViewModel() : base() {
            //base.InitializationCompleted += MainWindowVM_InitializationCompleted;
        }


        #region Overrides

        /// <summary>
        /// Initializes this instance.<para>
        /// Called by constructor. See also InitializationCompleted event.</para>
        /// </summary>
        protected override void Initialize() {
            base.Initialize();

            bool succ = this.initBeine();
            if (!succ) {
                Debug.WriteLine("Failure at MeinWindowViewModel.initBeine()", "Warning");
                return;
            }
            initBeinViewModels();
            _walker = new Stepper(_legs[0], _legs[1], _legs[2], _legs[3]);
            IsReadyToWalk = _walker.Ready && IsConnected;
            //Logger.Info("MainWindow ModelView intitialized.");
        }
        
        /// <summary>
        /// Disconnect from legs, save settings.
        /// </summary>
        protected override void OnDispose() {
            base.OnDispose();
            this.shutDownExecute();
            MySets.Default.Save();
        }

        #endregion

        #region private Helpers

        ///// <summary>
        ///// Setzt 'IsConnected' auf true, wenn Initialization erfolgreich.
        ///// </summary>
        ///// <param name="sender">The source of the event.</param>
        ///// <param name="e">The <see cref="System.ComponentModel.AsyncCompletedEventArgs"/> instance containing the event data.</param>
        //private void MainWindowVM_InitializationCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) {
        //    this.IsConnected = e.Error == null;
        //}

        void echoExecute(string param) {
            //string num = param as String;
            //if (num == null)
            //    return;
            switch (param/*num*/) {
                case "1":
                    _walker.Echo(1);
                    break;
                case "2":
                    _walker.Echo(2);
                    break;
                case "3":
                    _walker.Echo(3);
                    break;
                case "4":
                    _walker.Echo(4);
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Can execute the echo command?
        /// </summary>
        /// <param name="param">String Zahl 1 bis 4</param>
        /// <returns>true, wenn enstprechendes Bein im Status Done oder Initialized.</returns>
        bool echoCanExecute(string param) {
            if (!IsConnected) {
                return false;
            }

            int i = 0;
            if (Int32.TryParse(param/*num*/, out i)) {
                if (i < 1 || i > _legs.Length)
                    return false;
                if (_legs[i - 1] == null)
                    return false;
                return _legs[i - 1].State != BeinState.Working;
            }
            else
                return false;
        }

        bool shutDownCanExecute() {
            return IsConnected;
        }

        /// <summary>
        /// Closes TCP Connections an sets IsConnected=False.
        /// </summary>
        void shutDownExecute() {
            if (_legs == null)
                return;
            var shutDownTask = new Task(() => {
                foreach (var bein in _legs) {
                    if (bein == null)
                        continue;
                    bein.Dispose();
                }
            });
            shutDownTask.Start();
            this.IsConnected = false;
            this.IsReadyToWalk = false;
        }

        /// <summary>
        /// Executes the RunCommand count times.
        /// </summary>
        /// <param name="times">count as String. Number of step cyclus to execute.</param>
        void runExecute(string times) {
            if (null == times) {
                Debug.WriteLine("Missing RunCommand parameter! Action cancled.", "Warning");
                return;
            }
            switch (times) {
                case MainWindowViewModel.ONE:
                    runExecuteAsync(1);
                    break;
                case MainWindowViewModel.MULTI:
                    runExecuteAsync(this._multiple);
                    break;
                default:
                    break;
            }
        }

        void runExecuteAsync(int count) {
            var runTask = new Task(
                        cnt => {
                            IsReadyToWalk = false;
                            for (int i = 0; i < (int)cnt; i++) {
                                _walker.PerformStepCyclus();
                            }
                        },
                        count);
            runTask.ContinueWith(result => IsReadyToWalk = _walker.Ready);
            runTask.Start();
        }

        void stompExecute(string times) {
            if (null == times) {
                Debug.WriteLine("Missing StompCommand parameter! Action cancled.", "Warning");
                return;
            }
            switch (times) {
                case MainWindowViewModel.ONE:
                    runExecuteAsync(1);
                    break;
                case MainWindowViewModel.MULTI:
                    runExecuteAsync(this._multiple);
                    break;
                default:
                    Debug.WriteLine("Unknown StompCommand parameter: \"" + times + "\"! Action cancled.", "Warning");
                    break;
            }
        }

        void stompExecuteAsync(int count) {
            var runTask = new Task(
                        cnt => {
                            IsReadyToWalk = false;
                            for (int i = 0; i < (int)cnt; i++) {
                                _walker.PerformStampCyclus();
                            }
                            IsReadyToWalk = _walker.Ready;
                        },
                        count);
            runTask.Start();
        }

        void parkExecute() {
            Task.Run(() => {
                _walker.GoIntoParkPosition();
            });
        }

        bool parkCanExecute() {
            return this.IsReadyToWalk;
        }

        bool startPoseCanExecute() {
            return IsConnected && IsReadyToWalk;
        }

        void startPoseExecute() {
            Task.Run(() => {
                IsReadyToWalk = false;
                _walker.PerformWalkFastBase();
                IsReadyToWalk = _walker.Ready;
            });
        }

        void applySettingsExecute() {
            // Settings abspeichern
            Properties.Settings.Default.Save();
            reConnectExecute();
        }

        void reConnectExecute() {
            Task.Run(() => {
                _multiple = Properties.Settings.Default.Multiple;
                // Beine mit neuen Werten neu initialisieren
                this.initBeine();
                initBeinViewModels();
                // Stepper neu initialisieren.
                _walker = new Stepper(_legs[0], _legs[1], _legs[2], _legs[3]);
                IsReadyToWalk = true;
            });
        }

        /// <summary>
        /// Initializes the beine. On Success, sets IsConnected=true.
        /// </summary>
        /// <returns>True, wenn alle Beine initialisiert und verbunden sind. False, wenn fehlgeschlagen.</returns>
        /// <remarks>
        /// Führt als erstes shutDownExecute() aus: Bein.Dispose, IsConnected = false,...
        /// Initialisiert mittels der Settings neue Beine
        /// </remarks>
        bool initBeine() {
            // vorsichtshalber: schließe Connections
            this.shutDownExecute();
            _legs = new Bein[4];
            string ip = null;
            int port = -1;
            bool isCon = true;
            // Die Beine:
            try {
                // --- Vorne Links ---
                ip = Properties.Settings.Default.VLIP;
                port = Properties.Settings.Default.VLPort;
                Bein vl = new Bein(ip, port);
                _legs[VL] = vl;
                isCon = isCon && (vl.State != BeinState.NotConnected);
                Debug.WriteLineIf(isCon, "Bein VL connected at " + ip + ":" + port, "Info");
                // --- Vorne Rechts ---
                ip = Properties.Settings.Default.VRIP;
                port = Properties.Settings.Default.VRPort;
                Bein vr = new Bein(ip, port);
                _legs[VR] = vr;
                isCon = isCon && (vr.State != BeinState.NotConnected);
                Debug.WriteLineIf(isCon, "Bein VR connected at " + ip + ":" + port, "Info");
                // --- Hinten Links ---
                ip = Properties.Settings.Default.HLIP;
                port = Properties.Settings.Default.HLPort;
                Bein hl = new Bein(ip, port);
                _legs[HL] = hl;
                isCon = isCon && (hl.State != BeinState.NotConnected);
                Debug.WriteLineIf(isCon, "Bein HL connected at " + ip + ":" + port, "Info");
                // --- Hinten Rechts ---
                ip = Properties.Settings.Default.HRIP;
                port = Properties.Settings.Default.HRPort;
                Bein hr = new Bein(ip, port);
                _legs[HR] = hr;
                isCon = isCon && (hr.State != BeinState.NotConnected);
                Debug.WriteLineIf(isCon, "Bein HR connected at " + ip + ":" + port, "Info");
            }
            catch (BeinControlException bcEx) {
                System.Windows.MessageBox.Show(bcEx.Message);
                Debug.WriteLine(bcEx.StackTrace, "Error");
                this.shutDownExecute();
                return false;
            }
            setBeinParameter(_legs);
            this.IsConnected = _legs[0].State != BeinState.NotConnected && _legs[1].State != BeinState.NotConnected &&
                _legs[2].State != BeinState.NotConnected && _legs[3].State != BeinState.NotConnected;
            return true;
        }

        /// <summary>
        /// Sets the bein parameter (xyz-Werte).
        /// </summary>
        /// <param name="beine">Die beine. {vl, vr, hl, hr}</param>
        static void setBeinParameter(Bein[] beine) {
            int x, y, z = -1;

            //// --- Vorne Links ---
            x = MySets.Default.VL_UpX;
            y = MySets.Default.VL_UpY;
            z = MySets.Default.VL_UpZ;
            beine[VL].SetRaiseParameters(x, y, z);
            x = MySets.Default.VL_UpAheadX;
            y = MySets.Default.VL_UpAheadY;
            z = MySets.Default.VL_UpAheadZ;
            beine[VL].SetGrabFrontParameters(x, y, z);
            x = MySets.Default.VL_DownAheadX;
            y = MySets.Default.VL_DownAheadY;
            z = MySets.Default.VL_DownAheadZ;
            beine[VL].SetStepParameters(x, y, z);
            x = MySets.Default.VL_DownBackX;
            y = MySets.Default.VL_DownBackY;
            z = MySets.Default.VL_DownBackZ;
            beine[VL].SetPullParameters(x, y, z);
            //// --- Vorne Rechts ---
            x = MySets.Default.VR_UpX;
            y = MySets.Default.VR_UpY;
            z = MySets.Default.VR_UpZ;
            beine[VR].SetRaiseParameters(x, y, z);
            x = MySets.Default.VR_UpAheadX;
            y = MySets.Default.VR_UpAheadY;
            z = MySets.Default.VR_UpAheadZ;
            beine[VR].SetGrabFrontParameters(x, y, z);
            x = MySets.Default.VR_DownAheadX;
            y = MySets.Default.VR_DownAheadY;
            z = MySets.Default.VR_DownAheadZ;
            beine[VR].SetStepParameters(x, y, z);
            x = MySets.Default.VR_DownBackX;
            y = MySets.Default.VR_DownBackY;
            z = MySets.Default.VR_DownBackZ;
            beine[VR].SetPullParameters(x, y, z);
            //// --- Hinten Links ---
            x = MySets.Default.HL_UpX;
            y = MySets.Default.HL_UpY;
            z = MySets.Default.HL_UpZ;
            beine[HL].SetRaiseParameters(x, y, z);
            x = MySets.Default.HL_UpAheadX;
            y = MySets.Default.HL_UpAheadY;
            z = MySets.Default.HL_UpAheadZ;
            beine[HL].SetGrabFrontParameters(x, y, z);
            x = MySets.Default.HL_DownAheadX;
            y = MySets.Default.HL_DownAheadY;
            z = MySets.Default.HL_DownAheadZ;
            beine[HL].SetStepParameters(x, y, z);
            x = MySets.Default.HL_DownBackX;
            y = MySets.Default.HL_DownBackY;
            z = MySets.Default.HL_DownBackZ;
            beine[HL].SetPullParameters(x, y, z);
            //// --- Hinten Rechts ---
            x = MySets.Default.HR_UpX;
            y = MySets.Default.HR_UpY;
            z = MySets.Default.HR_UpZ;
            beine[HR].SetRaiseParameters(x, y, z);
            x = MySets.Default.HR_UpAheadX;
            y = MySets.Default.HR_UpAheadY;
            z = MySets.Default.HR_UpAheadZ;
            beine[HR].SetGrabFrontParameters(x, y, z);
            x = MySets.Default.HR_DownAheadX;
            y = MySets.Default.HR_DownAheadY;
            z = MySets.Default.HR_DownAheadZ;
            beine[HR].SetStepParameters(x, y, z);
            x = MySets.Default.HR_DownBackX;
            y = MySets.Default.HR_DownBackY;
            z = MySets.Default.HR_DownBackZ;
            beine[HR].SetPullParameters(x, y, z);
        }

        void initBeinViewModels() {
            this.BeinViewModels = new ObservableCollection<BeinViewModel>();
            Bein b = _legs[0];
            int id = Properties.Settings.Default.VLID;
            BeinViewModels.Add(new BeinViewModel(b, id.ToString()));
            b = _legs[1];
            id = Properties.Settings.Default.VRID;
            BeinViewModels.Add(new BeinViewModel(b, id.ToString()));
            b = _legs[2];
            id = Properties.Settings.Default.HLID;
            BeinViewModels.Add(new BeinViewModel(b, id.ToString()));
            b = _legs[3];
            id = Properties.Settings.Default.HRID;
            BeinViewModels.Add(new BeinViewModel(b, id.ToString()));
        }

        #endregion
    }
}
